#include <assert.h>
#include <string.h>	// memset

#include "view.h"
#include "region.h"
#include "visoal.h"

#define VIEW_CHUNK_SIZE		(128 * 1024)
#define RECT_CHUNK_SIZE		(64 * 1024)

typedef struct chunk_node
{
	listnode_t	ownerlist;
	uchar*		first_node;
	uchar*		first_free_node;
	uint		node_size;
	uint		free_node_head;
	uint		free_node_cnt;
	uint		total_node_cnt;
}
chunk_node_t;

#define CNT_PER_VIEW_CHK	((VIEW_CHUNK_SIZE - sizeof(chunk_node_t)) / sizeof(view_t))
#define CNT_PER_RECT_CHK	((RECT_CHUNK_SIZE - sizeof(chunk_node_t)) / sizeof(view_t))

typedef struct chunk_list
{
	// all used
	listnode_t used_chunk_list;
	// partial used
	listnode_t partial_chunk_list;
	// not used
	listnode_t empty_chunk_list;
}
chunk_list_t;

// this chunk list is used for view objects
static chunk_list_t view_ = {
	LISTNODE_INITIALIZER(view_.used_chunk_list),
	LISTNODE_INITIALIZER(view_.partial_chunk_list),
	LISTNODE_INITIALIZER(view_.empty_chunk_list),
};
// this chunk list is used for rect objects
static chunk_list_t rect_ = {
	LISTNODE_INITIALIZER(rect_.used_chunk_list),
	LISTNODE_INITIALIZER(rect_.partial_chunk_list),
	LISTNODE_INITIALIZER(rect_.empty_chunk_list),
};

// sync object for allocator
static sync_object_t view_allocator_sync_obj;
static sync_object_t rect_allocator_sync_obj;

// this function is not locked
static void* get_node_from_chunk(chunk_list_t* chunklist,
		chunk_node_t *chunk)
{
	void* ret = NULL;
	assert(NULL != chunklist && NULL != chunk);
	if (!chunk->free_node_cnt)
		return NULL;

	if (chunk->first_free_node
		< chunk->first_node + chunk->total_node_cnt * chunk->node_size)
	{
		ret = chunk->first_free_node;
		chunk->first_free_node += chunk->node_size;
	}
	else if (chunk->free_node_head)
	{
		ret = (void*)chunk->free_node_head;
		chunk->free_node_head = *((uint*)chunk->free_node_head);
	}

	if (!--chunk->free_node_cnt)
	{
		// move to used out list
		listnode_del(chunk->ownerlist);
		listnode_add(chunklist->used_chunk_list, chunk->ownerlist);
	}
	return ret;
}


// this function is not locked
chunk_node_t* alloc_chunk(uint chunksize, uint objsize, uint cnt)
{
	chunk_node_t* chunk;
	uchar* firstnode = (uchar*)malloc(chunksize);
	if (NULL == firstnode) return NULL;

	chunk = (chunk_node_t*)(firstnode + objsize * cnt);
	chunk->first_node = firstnode;
	chunk->first_free_node = firstnode;
	chunk->node_size = objsize;
	chunk->free_node_head = 0;		// NULL
	chunk->free_node_cnt = cnt;
	chunk->total_node_cnt = cnt;
	return chunk;
}

static chunk_node_t* do_get_chunk_by_node(listnode_t* lst, void* v)
{
	listnode_t* node = lst->next;
	for (; node != lst; node = node->next)
	{
		chunk_node_t* chunk = list_entry(chunk_node_t, ownerlist, node);
		uint _start = (uint)chunk->first_node;
		uint _end = _start + chunk->total_node_cnt * chunk->node_size;
		if (((uint)v) >= _start && ((uint)v) < _end)
			return chunk;
	}
	return NULL;
}

static chunk_node_t* get_chunk_by_node(chunk_list_t* chunklist, view_t* v)
{
	chunk_node_t* chunk = do_get_chunk_by_node(&(chunklist->partial_chunk_list), v);
	if (chunk) return chunk;
	return do_get_chunk_by_node(&(chunklist->used_chunk_list), v);
}

// ---- ctor & dtor ---------------
static void view_object_ctor(view_t* v)
{
	assert(NULL != v);

	memset(v, 0, sizeof(view_t));
	listnode_init(v->children);
	listnode_init(v->ownerlist);

	v->left	= -1;
	v->top	= -1;
	v->width = -1;
	v->height = -1;
}

static void rectlist_object_ctor(rectlist_node_t* r)
{
	memset(r, 0, sizeof(rectlist_node_t));
	r->r.left = -1;
	r->r.top = -1;
	r->r.width = -1;
	r->r.height = -1;
}

static void view_object_dtor(view_t* v)
{
	if (!v) return;
}

static void rectlist_object_dtor(rectlist_node_t* r)
{
	if (!r) return;
}

static void free_object(chunk_list_t* chunklist,
	sync_object_t* syncobj, void* v)
{
	uint* link;
	chunk_node_t *chunk;

	// need lock
	sync_start(syncobj);
	chunk = get_chunk_by_node(chunklist, v);
	if (NULL == chunk)
	{
		// todo: fatal error
		sync_end(syncobj);
		return;
	}

	link = (uint*)v;
	*link = chunk->free_node_head;
	chunk->free_node_head = (uint)v;

	// move list if necessary
	if (!chunk->free_node_cnt)
	{
		listnode_del(chunk->ownerlist);
		listnode_add(chunklist->partial_chunk_list, chunk->ownerlist);
	}
	if (++chunk->free_node_cnt == chunk->total_node_cnt)
	{
		listnode_del(chunk->ownerlist);
		listnode_add(chunklist->empty_chunk_list, chunk->ownerlist);
	}

	// unlock
	sync_end(syncobj);
}

void view_free(view_t* v)
{
	if (NULL == v)
		return;
	view_object_dtor(v);
	free_object(&view_, &view_allocator_sync_obj, v);
}

void rectlist_node_free(rectlist_node_t* nd)
{
	if (NULL == nd)
		return;
	rectlist_object_dtor(nd);
	free_object(&rect_, &rect_allocator_sync_obj, nd);
}

static void* alloc_object(chunk_list_t* chunklist, sync_object_t* syncobj,
		uint chunksize, uint objsize, uint objcntperchunk)
{
	view_t* ret;

	// need lock
	sync_start(syncobj);

	// try to get one from partial list
	if (!listnode_isempty(chunklist->partial_chunk_list))
	{
		listnode_t *node = chunklist->partial_chunk_list.next;
		chunk_node_t *chunk = list_entry(chunk_node_t, ownerlist, node);
		ret = get_node_from_chunk(chunklist, chunk);
	}
	else if (!listnode_isempty(chunklist->empty_chunk_list))
	{
		// try to get one from free list
		listnode_t *node = chunklist->empty_chunk_list.next;
		chunk_node_t* chunk = list_entry(chunk_node_t, ownerlist, node);
		ret = get_node_from_chunk(chunklist, chunk);
	}
	else	// allocate a brand new one
	{
		chunk_node_t* chunk = alloc_chunk(chunksize, objsize, objcntperchunk);
		if (NULL == chunk) return NULL;
		ret = get_node_from_chunk(chunklist, chunk);
		listnode_add(chunklist->partial_chunk_list, chunk->ownerlist);
	}

	// unlock
	sync_end(syncobj);
	return ret;
}

view_t* view_alloc(void)
{
	view_t* ret = (view_t*)alloc_object(&view_, &view_allocator_sync_obj,
		VIEW_CHUNK_SIZE, sizeof(view_t), CNT_PER_VIEW_CHK);
	if (NULL == ret) return NULL;
	view_object_ctor(ret);
	return ret;
}

rectlist_node_t* rectlist_node_alloc(void)
{
	rectlist_node_t* ret = (rectlist_node_t*)alloc_object(&rect_,
		&rect_allocator_sync_obj, RECT_CHUNK_SIZE,
		sizeof(rectlist_node_t), CNT_PER_RECT_CHK);
	if (NULL == ret) return NULL;
	rectlist_object_ctor(ret);
	return ret;
}

void global_init_allocator(void)
{
	prepare_sync_object(view_allocator_sync_obj);
	prepare_sync_object(rect_allocator_sync_obj);
}

void global_destroy_allocator(void)
{
	release_sync_object(rect_allocator_sync_obj);
	release_sync_object(view_allocator_sync_obj);
}
